iT邦幫忙

2023 iThome 鐵人賽

DAY 18
1
SideProject30

製作適用於網頁的台灣登山地圖系列 第 18

[Day18] 加入水域資料

  • 分享至 

  • xImage
  •  

前幾天處理的是山頭的點資料,今天就來看看如何把有面積的水域資料加入地圖之中。

加入新的 Tile Layer

有關登山活動會使用到的水域,在 OSM 中會被標記為:

  • natural=water: 天然水體
  • landuse=reservoir: 水庫
  • landuse=basin: 人工水體(蓄水池)

這些都是物件的型別都是 way,所以需要在 Lua 設定檔中的 way_function 處理。

除了物件的形狀外,我們也需要有 Layer 可以顯示水域的名字,因此在 config.json 中可以多加入以下的 Layer:

// config.json
"water": {
  "minzoom": 6,
  "maxzoom": 14,
  "simplify_below": 12,
  "simplify_level": 0.0003,
  "simplify_ratio": 2
},
"water_name": {
  "minzoom": 14,
  "maxzoom": 14
}

考慮縮放層級

另外,水域的面積也是大小有別的。我們會希望在縮放層級較小的圖磚中,只包含夠大的水域。這點可以使用 tilemaker 提供的函式 way:Area()(來源)

在開始之前,可以先查查 tilemaker 中,用於產製符合 OpenMapTiles schema 圖磚的設定檔。裡面有不少實用的自訂函式可以派上用場:

-- ZRES=Resolution for Zoom level
-- 若向量圖磚的「解析度」是 256x256,則每 pixel 是幾公尺
ZRES5  = 4891.97
ZRES6  = 2445.98
ZRES7  = 1222.99
ZRES8  = 611.5
ZRES9  = 305.7
ZRES10 = 152.9
ZRES11 = 76.4
ZRES12 = 38.2
ZRES13 = 19.1

-- 若 way 物件的面積在某一縮放層級的大小超過一個像素
-- 則開始在下一縮放層級中出現
function SetMinZoomByArea(way)
	local area=way:Area()
	if     area>ZRES5^2  then way:MinZoom(6)
	elseif area>ZRES6^2  then way:MinZoom(7)
	elseif area>ZRES7^2  then way:MinZoom(8)
	elseif area>ZRES8^2  then way:MinZoom(9)
	elseif area>ZRES9^2  then way:MinZoom(10)
	elseif area>ZRES10^2 then way:MinZoom(11)
	elseif area>ZRES11^2 then way:MinZoom(12)
	elseif area>ZRES12^2 then way:MinZoom(13)
	else                      way:MinZoom(14) end
end

有了SetMinZoomByArea這樣的函式,可以讓我們在不同縮放層級中,更有效率的篩選物件。因此相關設定檔可以撰寫為:

-- process.lua

...
-- 處理 way 物件
function way_function(way)
  local isClosed = way:IsClosed()      -- 封閉的 way,表示多邊形而非線段
  local natural  = way:Find("natural")
  local landuse  = way:Find("landuse")
  local water    = way:Find("water")

  -- 設定 water 圖層
  -- 篩選天然水域、水庫或人工蓄水池
  if natural == "water" or landuse == "reservoir" or landuse == "basin" then
    -- 若被覆蓋或不是封閉的多邊形,則不處理
    if way:Find("covered") == "yes" or not isClosed then return end
    
    -- 加入到 "water" Layer 中
    way:Layer("water", true)
    SetMinZoomByArea(way)

    -- 出現與否有季節性
    if way:Find("intermittent") == "yes" then way:Attribute("intermittent", 1) end
    
    -- 若有名稱,則將中心點加入到 "water_name" Layer
    if way:Holds("name") and natural == "water" then
      way:LayerAsCentroid("water_name")
      way:Attribute("name", way:Find("name"))
      SetMinZoomByArea(way)
    end
    return -- in case we get any landuse processing
  end
end

地圖樣式

使用兩個圖層分別渲染 water(水域) 和 water_name(水域名稱) Layer。

有關水域,可以撰寫如下:

{
  "id": "water",
  "type": "fill",
  "source": "taiwan",
  "source-layer": "water",
  "minzoom": 8,
  "paint": {
    "fill-color": "#BAD5FB"
  }
}

有關名稱,可以撰寫如下:

{
  "id": "water-name",
  "type": "symbol",
  "source": "taiwan",
  "source-layer": "water_name",
  "minzoom": 8,
  "filter": [
    "has",
    "name"
  ],
  "layout": {
    "symbol-placement": "point",
    "text-padding": 50,
    "text-field": "{name}",
    "text-size": 24 
  },
  "paint": {
    "text-color": "#2588b6",
    "text-halo-color": "white",
    "text-halo-width": 3
  }
}

以日月潭為例,最終產出的成果為:
result

今天使用到的程式碼可以在 Github 中找到。


上一篇
[Day17] 精進山頭標示
下一篇
[Day19] tilemaker 細部設定
系列文
製作適用於網頁的台灣登山地圖25
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言